/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.project;

import cn.ac.nercis.pes.common.constant.project.ProjectState;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.project.ProjectDTO;
import cn.ac.nercis.pes.model.dto.project.ProjectDetailDTO;
import cn.ac.nercis.pes.repository.dal.manage.TestedUnitRepository;
import cn.ac.nercis.pes.repository.dal.project.ProjectRepository;
import cn.ac.nercis.pes.repository.dal.project.SystemRepository;
import cn.ac.nercis.pes.repository.mapper.project.ProjectMapper;
import cn.ac.nercis.pes.repository.model.manage.TestedUnitDO;
import cn.ac.nercis.pes.repository.model.project.ProjectDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;


/**
 * 项目管理服务
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class ProjectService {
    private final ProjectRepository projectRepository;
    private final TestedUnitRepository testedUnitRepository;
    private final SystemRepository systemRepository;

    @Autowired
    public ProjectService(ProjectRepository projectRepository,TestedUnitRepository testedUnitRepository,
                          SystemRepository systemRepository) {
        this.projectRepository = projectRepository;
        this.testedUnitRepository = testedUnitRepository;
        this.systemRepository = systemRepository;
    }

    public Page<ProjectDTO> findAll(String projectName, String projectNumber, Pageable pageable){
        ProjectDO example = ProjectDO.builder().name(projectName).number(projectNumber).build();
        ExampleMatcher matcher = ExampleMatcher.matchingAny()
                .withMatcher("name",ExampleMatcher.GenericPropertyMatchers.contains())
                .withMatcher("number",ExampleMatcher.GenericPropertyMatchers.exact());
        return this.projectRepository.findAll(Example.of(example,matcher),pageable)
                .map(ProjectMapper.INSTANCE::toDTO);
    }

    public List<ProjectDTO> findAll(String searchText){
        if(StringUtils.isNotBlank(searchText)){
            return this.projectRepository.findAll()
                    .stream()
                    .filter(project-> StringUtils.contains(project.getNumber(),searchText)
                            || StringUtils.contains(project.getName(),searchText))
                    .map(ProjectMapper.INSTANCE::toDTO)
                    .collect(Collectors.toList());
        }else{
            return this.projectRepository.findAll()
                    .stream()
                    .map(ProjectMapper.INSTANCE::toDTO)
                    .collect(Collectors.toList());
        }
    }

    public Result<ProjectDTO> save(ProjectDetailDTO projectDetail){
        try{
            if(StringUtils.isEmpty(projectDetail.getId())){
                return create(projectDetail);
            }else{
                return modify(projectDetail);
            }
        }catch (Exception e){
            log.error("保存项目出错：",e);
            return Result.failed(ResultCode.FAIL);
        }
    }

    public Result<String> delete(final String id){
        if(systemRepository.existsByProjectId(id)){
            return Result.failed(ResultCode.PROJECT_SYSTEM_EXIST);
        }
        try{
            return projectRepository.findById(id)
                    .map(projectDO -> {
                        projectRepository.delete(projectDO);
                        return projectDO;
                    })
                    .map(projectDO -> Result.success(projectDO.getId()))
                    .orElse(Result.failed(ResultCode.FAIL));
        }catch (Exception ex){
            log.error("删除项目出错：",ex);
            return Result.failed(ResultCode.FAIL);
        }
    }

    private Result<ProjectDTO> create(ProjectDetailDTO projectDetail){
        if(projectRepository.existsByNumber(projectDetail.getNumber())){
            return Result.failed(ResultCode.PROJECT_NUMBER_EXIST);
        }
        Optional<TestedUnitDO> testedUnitDO = this.testedUnitRepository.findById(projectDetail.getTestedUnitId());
        return testedUnitDO.map(unitDO -> Optional.of(ProjectMapper.INSTANCE.toDO(projectDetail))
                .map(pd -> {
                    pd.setTestedUnit(unitDO.getName());
                    pd.setState(ProjectState.DEFAULT);
                    pd.setCreateTime(LocalDateTime.now());
                    return pd;
                })
                .map(projectRepository::save)
                .map(ProjectMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL)))
                .orElse(Result.failed(ResultCode.TESTED_UNIT_NOT_EXIST));
    }

    private Result<ProjectDTO> modify(ProjectDetailDTO projectDetail){
        ProjectDO projectDO = this.projectRepository.findById(projectDetail.getId()).orElse(null);
        if(Objects.isNull(projectDO)){
            return Result.failed(ResultCode.PROJECT_NOT_EXIST);
        }
        if(!StringUtils.equals(projectDO.getNumber(),projectDetail.getNumber())
                && this.projectRepository.existsByNumber(projectDetail.getNumber())){
            return Result.failed(ResultCode.PROJECT_NUMBER_EXIST);
        }
        Optional<TestedUnitDO> testedUnitDO = this.testedUnitRepository.findById(projectDetail.getTestedUnitId());
        if(testedUnitDO.isEmpty()){
            return Result.failed(ResultCode.TESTED_UNIT_NOT_EXIST);
        }
        return testedUnitDO.map(tu->{
            projectDO.setNumber(projectDetail.getNumber());
            projectDO.setName(projectDetail.getName());
            projectDO.setRequesterUnit(projectDetail.getRequesterUnit());
            projectDO.setTestedUnitId(projectDetail.getTestedUnitId());
            projectDO.setTestedUnit(testedUnitDO.get().getName());
            projectDO.setState(ProjectState.DEFAULT);
            projectDO.setModifyTime(LocalDateTime.now());
            return projectDO;
        })
                .map(projectRepository::save)
                .map(ProjectMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }

}